//
// Created by Arnold on 2021/5/18.
//

#include "lx_serial.h"
#include <fcntl.h> // Contains file controls like O_RDWR
#include <errno.h> // Error integer and strerror() function
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h> // write(), read(), close()
#include <sys/file.h>
#include <pthread.h>
#include <sys/select.h>

static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER;

static EP_INT lx_serial_attr_set(EP_INT fd, EP_INT baud_rate, EP_CHAR data_bit, EP_CHAR parity, EP_CHAR stop_bit);


EP_INT lx_serial_open(const char *serial_dev, int n_speed)
{
    EP_INT fd;
    fd = open(serial_dev, O_RDWR | O_NOCTTY);
    printf("dev:%s, speed:%d, \n", serial_dev, n_speed);
    if (fd == -1)
    {
        perror("open serail port failed  : ");
    }
    CHECK_FAILED(fd, 0);
    //锁定串口，独占使用
    CHECK_FAILED(flock(fd, LOCK_EX | LOCK_NB), 1);
    //设置串口参数
    CHECK_FAILED(lx_serial_attr_set(fd, n_speed, '8', 'n', '1'), 2);
    return fd;
    failed_2:
    flock(fd, LOCK_UN);
    perror("set serial port attr failed");
    failed_1:
    close(fd);
    perror("lock serial port failed");
    failed_0:
    perror("open serial port failed ");
    perror("serial port: ");
    return RET_FAILURE;
}

EP_INT lx_serial_read_char(EP_INT fd, EP_CHAR *ch)
{
    return read(fd, ch, 1);
}
/**
 * 一直读
*/
EP_INT lx_serial_read_until(EP_INT fd, EP_CHAR *buf, EP_INT buf_size)
{
    EP_INT current_recv = 0;
    EP_INT ret;

    for (;;)
    {
        ret = read(fd, buf + current_recv, buf_size - current_recv);
        CHECK_FAILED(ret, 0);
        current_recv += ret;
        if (current_recv == buf_size)
        {
            break;
        }
    }
    return RET_SUCCESS;

    failed_0:
    return RET_FAILURE;
}

EP_INT lx_serial_read_wait(EP_INT fd, EP_CHAR *buf, EP_INT buf_size, EP_INT wait_sec)
{
    EP_INT ret;
    EP_INT i;
    struct timeval tv;
    fd_set readfds;


    for (i = 0; i < buf_size; i++)
    {
        EP_MEMSET(&tv);
        tv.tv_sec = wait_sec;
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        ret = select(fd + 1, &readfds, NULL, NULL, &tv);
        if (ret == -1)
        {
            perror("select for serial failed");
        }
        else if (ret == 0)
        {
            perror("read serail time out ");
            goto failed;
        }
        else
        {
            read(fd, buf + i, 1);
        }
    }

    return RET_SUCCESS;
    failed:
    return RET_FAILURE;
}

EP_INT lx_serial_read_time(EP_INT fd, EP_CHAR *buf, EP_INT buf_size, EP_INT time_sec)
{
    EP_INT ret;
    EP_INT i;
    struct timeval tv;
    fd_set readfds;


    for (i = 0; i < buf_size; i++)
    {
        EP_MEMSET(&tv);
        tv.tv_sec = time_sec;
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        ret = select(fd + 1, &readfds, NULL, NULL, &tv);
        if (ret == -1)
        {
            perror("select for serial failed");
        }
        else if (ret == 0)
        {
            break;
        }
        else
        {
            read(fd, buf + i, 1);
        }
    }

    return RET_SUCCESS;
}

EP_INT lx_serial_write(EP_INT fd, EP_VOID *buf, EP_INT buf_size)
{
    EP_INT ret = 0;
    pthread_mutex_lock(&write_lock);
    ret = write(fd, buf, buf_size);
    if (ret != buf_size)
    {
        pthread_mutex_unlock(&write_lock);
        return RET_FAILURE;
    }
    else
    {
        pthread_mutex_unlock(&write_lock);
        return RET_SUCCESS;
    }
}

EP_VOID lx_serial_close(EP_INT fd)
{
    close(fd);
}


static EP_INT lx_serial_attr_set(EP_INT fd, EP_INT baud_rate, EP_CHAR data_bit, EP_CHAR parity, EP_CHAR stop_bit)
{
    EP_INT baud_type;
    EP_INT data_bit_type;
    EP_INT parity_type;
    EP_INT stop_bit_type;


    switch (baud_rate)
    {
        case 50 :
            baud_type = B50;
            break;
        case 75 :
            baud_type = B75;
            break;
        case 110 :
            baud_type = B110;
            break;
        case 134 :
            baud_type = B134;
            break;
        case 150 :
            baud_type = B150;
            break;
        case 200 :
            baud_type = B200;
            break;
        case 300 :
            baud_type = B300;
            break;
        case 600 :
            baud_type = B600;
            break;
        case 1200 :
            baud_type = B1200;
            break;
        case 1800 :
            baud_type = B1800;
            break;
        case 2400 :
            baud_type = B2400;
            break;
        case 4800 :
            baud_type = B4800;
            break;
        case 9600 :
            baud_type = B9600;
            break;
        case 19200 :
            baud_type = B19200;
            break;
        case 38400 :
            baud_type = B38400;
            break;
        case 57600 :
            baud_type = B57600;
            break;
        case 115200 :
            baud_type = B115200;
            break;
        case 230400 :
            baud_type = B230400;
            break;
        case 460800 :
            baud_type = B460800;
            break;
        case 500000 :
            baud_type = B500000;
            break;
        case 576000 :
            baud_type = B576000;
            break;
        case 921600 :
            baud_type = B921600;
            break;
        case 1000000 :
            baud_type = B1000000;
            break;
        case 1152000 :
            baud_type = B1152000;
            break;
        case 1500000 :
            baud_type = B1500000;
            break;
        case 2000000 :
            baud_type = B2000000;
            break;
        case 2500000 :
            baud_type = B2500000;

        default      :
            perror("invalid baud rate");
            return RET_FAILURE;
            break;
    }

    switch (data_bit)
    {
        case '8':
            data_bit_type = CS8;
            break;
        case '7':
            data_bit_type = CS7;
            break;
        case '6':
            data_bit_type = CS6;
            break;
        case '5':
            data_bit_type = CS5;
            break;
        default :
            perror("invalid data bit");
            return RET_FAILURE;
            break;
    }
    switch (parity)
    {
        case 'N':
        case 'n':
            parity_type = 0;
            parity_type = IGNPAR;
            break;
        case 'E':
        case 'e':
            parity_type = PARENB;
            parity_type = INPCK;
            break;
        case 'O':
        case 'o':
            parity_type = (PARENB | PARODD);
            parity_type = INPCK;
            break;
        default :
            perror("invalid parity\n");
            return RET_FAILURE;
            break;
    }
    switch (stop_bit)
    {
        case '1':
            stop_bit_type = 0;
            break;
        case '2':
            stop_bit_type = CSTOPB;
            break;
        default :
            perror("invalid number of stop bits\n");
            return RET_FAILURE;
            break;
    }

    struct termios term_info;
    EP_MEMSET(&term_info);

    term_info.c_cflag &= ~CSIZE;
    term_info.c_cflag |= data_bit_type;
    term_info.c_cflag |= (CLOCAL | CREAD);
    term_info.c_cflag &= ~CSTOPB;
    term_info.c_cflag |= stop_bit_type;
    term_info.c_cflag &= ~PARENB;
    term_info.c_cflag |= parity_type;

    term_info.c_cc[VTIME] = 0;    // block
    term_info.c_cc[VMIN] = 1;     //every char

    cfsetispeed(&term_info, baud_type);
    cfsetospeed(&term_info, baud_type);

    CHECK_FAILED(tcsetattr(fd, TCSANOW, &term_info), 0);

    return RET_SUCCESS;

    failed_0:
    return RET_FAILURE;
}